#include "Mesh.hpp"
#include "Quaternion.hpp"
#include "MathHelper.hpp"

using namespace Typhoon;

Mesh::Mesh() : BoundingBox(this) {
	BoundingBox.Method+=event_handler(this, &Mesh::CalcBoundingBox);
}

Mesh::~Mesh() {
}

void Mesh::CalcBoundingBox( DirtyProperty<Mesh*, Box>::EventData& eventData )
{
#define MAX 10000000;
	Box& box = *eventData.Value;
	box.left = MAX;
	box.right = -MAX;
	box.top = MAX;
	box.bottom = -MAX;

	for (int i=0; i<vertices.size(); i++) {
		Vertex& vertex = vertices[i];
		if (vertex.Position.x<box.left) box.left = vertex.Position.x;
		if (vertex.Position.x>box.right) box.right = vertex.Position.x;
		if (vertex.Position.y<box.top) box.top = vertex.Position.y;
		if (vertex.Position.y>box.bottom) box.bottom = vertex.Position.y;
	}
}

Mesh::VerticesList& Mesh::Vertices() {
	BoundingBox.MakeDirty();
	return vertices;
}

Mesh::TrianglesList& Mesh::Triangles() {
	return triangles;
}

const Mesh::VerticesList& Mesh::ReadOnlyVertices() {
	return vertices;
}

const Mesh::TrianglesList& Mesh::ReadOnlyTriangles() {
	return triangles;
}


void Mesh::AddTexturedQuad( Texture* texture, const Box& textureCoords, const Matrix3x3& transform )
{
	Vector2 size = textureCoords.Size() * Vector2(texture->GetWidth(), texture->GetHeight());
	size *= 0.5f;

	Colour color = Colour::White();
	GLshort index = Vertices().size();
	Vector3 normal = Vector3(0,0,1);

	Vertices().push_back(Vertex(transform.TransformPoint(-size),Vector2(textureCoords.left,textureCoords.top), normal, color));
	Vertices().push_back(Vertex(transform.TransformPoint(Vector2(size.x, -size.y)),Vector2(textureCoords.right,textureCoords.top), normal, color));
	Vertices().push_back(Vertex(transform.TransformPoint(size),Vector2(textureCoords.right,textureCoords.bottom), normal,color));
	Vertices().push_back(Vertex(transform.TransformPoint(Vector2(-size.x, size.y)),Vector2(textureCoords.left,textureCoords.bottom), normal,color));

	Triangles().push_back((GLshort)(index));
	Triangles().push_back((GLshort)(index+1));
	Triangles().push_back((GLshort)(index+2));
	Triangles().push_back((GLshort)(index));
	Triangles().push_back((GLshort)(index+2));
	Triangles().push_back((GLshort)(index+3));

}

void Mesh::AddTexturedQuad(TextureAtlas* atlas, const std::string& name ) {
	AddTexturedQuad(atlas, atlas->GetTextureCoords(name), Matrix3x3::CreateIdentity());
}

void Mesh::AddTexturedQuad( TextureAtlas* atlas, const std::string& name, float scale ) {
	AddTexturedQuad(atlas, atlas->GetTextureCoords(name), Matrix3x3::CreateScale(scale));
}

void Mesh::AddTexturedQuad( TextureAtlas* atlas, const std::string& name, float scale, Vector2 center, float rotation ) {
	AddTexturedQuad(atlas, atlas->GetTextureCoords(name), Matrix3x3::CreateScale(scale) * Matrix3x3::CreateRotation(rotation) * Matrix3x3::CreateTranslation(center));
}

 
void Mesh::AddQuad( Vector2 center, Vector2 size, Colour color )
{
	Vector2 sizeHalf = size * 0.5f;

	GLshort index = Vertices().size();
	Vector3 normal = Vector3(0,0,1);

	Vertices().push_back(Vertex(center-sizeHalf,Vector2(0,0), normal, color));
	Vertices().push_back(Vertex(center+Vector2(sizeHalf.x, -sizeHalf.y),Vector2(1,0), normal, color));
	Vertices().push_back(Vertex(center+sizeHalf,Vector2(1,1), normal,color));
	Vertices().push_back(Vertex(center+Vector2(-sizeHalf.x, sizeHalf.y),Vector2(0,1), normal,color));

	Triangles().push_back((GLshort)(index));
	Triangles().push_back((GLshort)(index+1));
	Triangles().push_back((GLshort)(index+2));
	Triangles().push_back((GLshort)(index));
	Triangles().push_back((GLshort)(index+2));
	Triangles().push_back((GLshort)(index+3));
}

void Mesh::AddTriangle(Typhoon::Vector2 p1, Typhoon::Vector2 p2, Typhoon::Vector2 p3, Typhoon::Colour color) {
    
    GLshort index = Vertices().size();
	Vector3 normal = Vector3(0,0,1);

    Vertices().push_back(Vertex(p1,Vector2(0,0), normal, color));
    Vertices().push_back(Vertex(p2,Vector2(0,0), normal, color));
    Vertices().push_back(Vertex(p3,Vector2(0,0), normal, color));
    
    Triangles().push_back((GLshort)(index));
	Triangles().push_back((GLshort)(index+1));
	Triangles().push_back((GLshort)(index+2));
}

void Mesh::SetColor(const Colour& color) {
	for(int i=0; i<vertices.size(); i++) {
		Vertex& vertex = vertices[i];
		vertex.Color = color;
	}
}

void Mesh::SetAlpha(const float& alpha) {
	for(int i=0; i<vertices.size(); i++) {
		Vertex& vertex = vertices[i];
		vertex.Color.a = alpha;
	}
}

void Mesh::Clear() {
    Vertices().clear();
    Triangles().clear();
}

void Mesh::AddCube(Vector3 center, Vector3 size, Colour color) {

	Matrix4x4 front = Matrix4x4::CreateTransform(center + Vector3(0,0,size.z), size, Quaternion::Create(0, Vector3(0,0,1)));
	Matrix4x4 back = Matrix4x4::CreateTransform(center + Vector3(0,0,-size.z), size, Quaternion::Create(ToRadians(180), Vector3(1,0,0)));
	Matrix4x4 left = Matrix4x4::CreateTransform(center + Vector3(size.x,0,0), size, Quaternion::Create(ToRadians(90), Vector3(0,1,0)));
	Matrix4x4 right = Matrix4x4::CreateTransform(center + Vector3(-size.x,0,0), size, Quaternion::Create(ToRadians(-90), Vector3(0,1,0)));
	Matrix4x4 top = Matrix4x4::CreateTransform(center + Vector3(0,size.y,0), size, Quaternion::Create(ToRadians(-90), Vector3(1,0,0)));
	Matrix4x4 bottom = Matrix4x4::CreateTransform(center + Vector3(0,-size.y,0), size, Quaternion::Create(ToRadians(90), Vector3(1,0,0)));
	
	Colour colorFront = Colour::Red();
	Colour colorBack = Colour::Yellow();
	Colour colorLeft = Colour::Blue();
	Colour colorRight = Colour::Green();
	Colour colorTop = Colour::White();
	Colour colorBottom = Colour(0,255,(Colour::Component)255,255);

	Colour c = Colour::White();

	 colorFront = c;
 colorBack = c;
 colorLeft = c;
 colorRight = c;
 colorTop = c;
 colorBottom = c;

	Vector3 n = Vector3(0,0,1);

	AddTriangle(Vector3(1,1,0), Vector3(-1,1,0), Vector3(-1,-1,0),n,n,n, Vector2(0,0), Vector2(1,0), Vector2(1,1), colorFront, front);
	AddTriangle(Vector3(1,1,0), Vector3(-1,-1,0), Vector3(1,-1,0),n,n,n, Vector2(0,0), Vector2(1,1), Vector2(0,1), colorFront, front);

	AddTriangle(Vector3(1,1,0), Vector3(-1,1,0), Vector3(-1,-1,0),n,n,n, Vector2(0,0), Vector2(1,0), Vector2(1,1), colorBack, back);
	AddTriangle(Vector3(1,1,0), Vector3(-1,-1,0), Vector3(1,-1,0),n,n,n, Vector2(0,0), Vector2(1,1), Vector2(0,1), colorBack, back);

	AddTriangle(Vector3(1,1,0), Vector3(-1,1,0), Vector3(-1,-1,0),n,n,n, Vector2(0,0), Vector2(1,0), Vector2(1,1), colorLeft, left);
	AddTriangle(Vector3(1,1,0), Vector3(-1,-1,0), Vector3(1,-1,0),n,n,n, Vector2(0,0), Vector2(1,1), Vector2(0,1), colorLeft, left);

	AddTriangle(Vector3(1,1,0), Vector3(-1,1,0), Vector3(-1,-1,0),n,n,n, Vector2(0,0), Vector2(1,0), Vector2(1,1), colorRight, right);
	AddTriangle(Vector3(1,1,0), Vector3(-1,-1,0), Vector3(1,-1,0),n,n,n, Vector2(0,0), Vector2(1,1), Vector2(0,1), colorRight, right);

	AddTriangle(Vector3(1,1,0), Vector3(-1,1,0), Vector3(-1,-1,0),n,n,n, Vector2(0,0), Vector2(1,0), Vector2(1,1), colorTop, top);
	AddTriangle(Vector3(1,1,0), Vector3(-1,-1,0), Vector3(1,-1,0),n,n,n, Vector2(0,0), Vector2(1,1), Vector2(0,1), colorTop, top);

	AddTriangle(Vector3(1,1,0), Vector3(-1,1,0), Vector3(-1,-1,0),n,n,n, Vector2(0,0), Vector2(1,0), Vector2(1,1), colorBottom, bottom);
	AddTriangle(Vector3(1,1,0), Vector3(-1,-1,0), Vector3(1,-1,0),n,n,n, Vector2(0,0), Vector2(1,1), Vector2(0,1), colorBottom, bottom);

}

void Mesh::AddTriangle(
	const Vector3& p1, const Vector3& p2, const Vector3& p3,
	const Vector3& n1, const Vector3& n2, const Vector3& n3,
	const Vector2& texCoord1, const Vector2& texCoord2, const Vector2& texCoord3, 
	const Colour& color, const Matrix4x4& world) {
	GLshort index = Vertices().size();

	Vector3 nn1 = world.TransformVector(n1); nn1.Normalize();
	Vector3 nn2 = world.TransformVector(n2); nn2.Normalize();
	Vector3 nn3 = world.TransformVector(n3); nn3.Normalize();

    Vertices().push_back(Vertex(world.TransformPosition(p1), texCoord1, nn1, color));
    Vertices().push_back(Vertex(world.TransformPosition(p2), texCoord2, nn2, color));
    Vertices().push_back(Vertex(world.TransformPosition(p3), texCoord3, nn3, color));
    
    Triangles().push_back((GLshort)(index));
	Triangles().push_back((GLshort)(index+1));
	Triangles().push_back((GLshort)(index+2));

}